package com.blog.cloud.security.config;

import com.blog.cloud.security.filter.JwtAuthenticationFilter;
import com.blog.cloud.security.handler.BlogAccessDeniedHandler;
import com.blog.cloud.security.handler.BlogAuthenticationEntryPoint;
import com.blog.cloud.security.provider.MobileAuthenticationProvider;
import com.blog.cloud.security.service.UserDetailService;
import com.blog.cloud.sms.service.SmsService;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;

@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtAuthenticationFilter jwtAuthenticationFilter;
    private final UserDetailService userDetailService;
    private final SmsService tencentSmsService;

    // 访问白名单，在该类初始化的时候就需要获取
    //    String[] whitelist = {"/sms/**","/auth/**","/actuator/**"};

    private static String[] whitelist = {};
    // 可以在静态代码块中完成，当类被加载时被初始化
    static {
        // TODO 查询数据库白名单表
        List<String> result = new ArrayList<>();
        result.add("/sms/**");
        result.add("/auth/**");
        result.add("/actuator/**");
        if (!CollectionUtils.isEmpty(result)) {
            whitelist = result.toArray(String[]::new);
        }
    }

    @Bean
    @SneakyThrows
    public SecurityFilterChain securityFilterChain(HttpSecurity http) {
        // 全局请求策略配置
        http
                .csrf().disable()       // 禁用csrf跨站请求伪造
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers(whitelist).permitAll()     // 白名单全部放行
                        .requestMatchers(HttpMethod.OPTIONS).permitAll()        // options请求放行
                        .anyRequest().authenticated()       // 其他请求需要认证
                )
                // 禁用session，改用JWT的方式
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .rememberMe().rememberMeParameter("remember-me").key("").and()
                // 添加自定义认证过滤器，按照默认的security所有过滤器顺序
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                // 设置自定义认证、鉴权异常处理
                .exceptionHandling()
                // 自定义认证异常处理
                .authenticationEntryPoint(blogAuthenticationEntryPoint())
                // 自定义鉴权异常处理
                .accessDeniedHandler(blogAccessDeniedHandler());

        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager() {
        List<AuthenticationProvider> providers = new ArrayList<>(3);
        // 可以设置多个认证处理器
        AuthenticationProvider usernameAuthenticationProvider = new DaoAuthenticationProvider();
        MobileAuthenticationProvider mobileAuthenticationProvider = new MobileAuthenticationProvider(userDetailService,tencentSmsService);
        providers.add(usernameAuthenticationProvider);
        providers.add(mobileAuthenticationProvider);
        return new ProviderManager(providers);
    }



    private BlogAuthenticationEntryPoint blogAuthenticationEntryPoint() {
        return new BlogAuthenticationEntryPoint();
    }

    private BlogAccessDeniedHandler blogAccessDeniedHandler() {
        return new BlogAccessDeniedHandler();
    }


    // 笔记1： 凡是在springsecurity的认证和授权过程中抛出的异常，不论是我们认为抛出的异常还是走源码过程抛出的异常都会被springsecurity过滤器链中的ExceptionTranslationFilter异常处理的过滤器捕获
    // 并调用相应的接口方法进行异常处理，所以会导致这些异常的响应结果和我们自定义的响应结果不一致的情况，为了保证给前端的响应结果的一致性，我们有必要对相应的接口
    // 进行自定义的实现，以此来保证响应结果的统一性。同时在完成自定义处理之后，我们必须将实现注册成Bean，并将他们加入到springsecurity过滤环节的对应节点，
    // 也就是在HttpSecurity对象的对应节点进行配置，以此确保自定义实现的组件能够生效。相应接口有：认证失败处理AuthenticationEntryPoint、鉴权失败处理AccessDeniedHandler

    // 笔记2：  自从springsecurity-5.7.X版本开始，官方将废弃WebSecurityConfigurerAdapter这个springsecurity的适配器类，转而推荐使用向容器注入SecurityFilterChain
    // 安全过滤器链的方法配置springsecurity

    // 笔记3：  spring官方推荐使用构造函数的方式完成依赖注入，相应的我们可以使用lombok的相应注解完成，在开发过程中应当减少使用像@Autowired、@Resource的注解

    // 笔记4： 在我们对springsecurity进行配置时，如果我们不对HttpSecurity对象配置formLogin属性的话，则在过滤器链中将不会存在UsernamePasswordAuthenticationFilter过滤器
    // 原因是在springsecurity的默认配置中为HttpSecurity对象配置了formLogin属性，在该属性中配置了一个默认的登陆页面，同时new了一个UsernamePasswordAuthenticationFilter放了容器
    // 但如果我们自定义配置springsecurity时没有配置formLogin属性则不会走UsernamePasswordAuthenticationFilter这一套的过滤器逻辑。所以，对于认证和授权的实现方案，我们可以总结为两套，
    // 一套是，走我们自定义的过滤器并没有配置formLogin属性的认证方案；另一套是，走springsecurity默认的通过走UsernamePasswordAuthenticationFilter的这一套方案，走这套方案需要我们
    // 在对springsecurity进行自定配置是为HttpSecurity对象配置formLogin属性，此外，我们还可以对这套方案中的一些流程处理进行自定义实现，比如自定义实现AuthenticationSuccessHandler
    // AuthenticationFailureHandler等一些关键性流程的自我定制化实现，以此来更好的完成我们的认证授权。此外、如果我们也想对登出操作进行自定义的话，我们同理可以对HttpSecurity对象配置logout属性
    // 然后自定义实现LogoutSuccessHandler

    // SecurityContextHolderFilter、AuthorizationFilter
    // 如果我们不是用默认的formLogin的方式，则需要自定义认证过滤器，然后在其中使用 AuthenticationManager 来认证登录信息
}
